﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Linq.Expressions;
using UnitTests.Logging;

namespace UnitTests
{
  public class Logger : LoggableObject, IDisposable, IAliasProvider
  {
    private readonly Dictionary<LoggableObject, string> _loggableObjects;

    public Logger(params Tuple<LoggableObject, string>[] loggableObjects)
    {
      _loggableObjects = Init(loggableObjects);
    }
    public Logger(params Expression<Func<object>>[] props)
    {
      _loggableObjects = Init(from p in props select Tuple.Create(GetValue(p), GetName(p)));
    }

    public LoggableObject GetValue(Expression<Func<object>> propertyExpression)
    {
      var res = propertyExpression.Compile()();
      var loggableObject = res as LoggableObject;
      if (loggableObject == null && res != null)
      {
        var colleciton = res as INotifyCollectionChanged;
        if (colleciton != null)
        {
          return new ObservableCollectionLogger(colleciton);
        }
        throw new NotSupportedException();
      }
      return loggableObject;
    }

    public string GetName(Expression<Func<object>> propertyExpression)
    {
      var lambda = (LambdaExpression)propertyExpression;
      return GetMemberExpressionName(lambda.Body as MemberExpression);
    }
    public string GetMemberExpressionName(MemberExpression exp)
    {
      if (exp == null) return null;
      string prev = GetMemberExpressionName(exp.Expression as MemberExpression);
      if (prev != null)
        prev += ".";
      return prev + exp.Member.Name;
    }
    private Dictionary<LoggableObject, string> Init(IEnumerable<Tuple<LoggableObject, string>> loggableObjects)
    {
      var res = loggableObjects.ToDictionary(l => l.Item1, l => l.Item2);
      foreach (var loggableObject in res.Keys)
        loggableObject.LogMessage += LoggableObjectOnLogMessage;
      return res;
    }

    private void LoggableObjectOnLogMessage(object sender, LogMessageEventArgs e)
    {
      e.Message.Resolve(this);
      AppendLog(e.Message);
    }

    public void Dispose()
    {
      foreach (var loggableObject in _loggableObjects.Keys)
      {
        if (loggableObject is ITracker)
        {
          ((IDisposable) loggableObject).Dispose();
        }
        loggableObject.LogMessage -= LoggableObjectOnLogMessage;
      }
    }

    public string GetAlias(LoggableObject obj)
    {
      return _loggableObjects[obj];
    }
  }
}